前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
  • 基础篇

    • 环境搭建
    • node部署
    • 基础应用
  • 内置模块

    • 1.0 本地路径处理 path
    • 2.0 文件系统操作 fs
    • 3.1 基础调试 console
    • 3.2 本地调试远程服务器上的Node代码
    • 4.1 网络服务 http
    • 4.2 网络服务 http res
    • 4.3 网络服务 http req
    • 4.4 网络服务 http server
    • 4.5 网络服务 http client
    • 4.6 网络服务 https
    • 4.7 网络TCP net
    • 4.8 网络UDP dgram
    • 4.9 域名解析 dns
    • 5.0 网络地址解析 url
    • 5.1 URL查询字符串 querystring
    • 6.1 流操作 stream
    • 6.2 逐行读取 readline
    • 7.1 进程相关 process
    • 7.2 子进程 child
    • 8.1 二进制数据 buffer
    • 8.2 二进制解码 string_decoder
    • 9.1 事件机制 events
    • 9.2 实用工具模块 util
    • 9.3 数据加密 crypto
    • 9.4 MD5入门介绍及crypto模块的应用
    • 9.4 资源压缩 zlib
    • 9.5 集群 cluster
    • 9.6 v8
  • 进阶篇

    • 5分钟入门非对称加密用法
    • cluster
    • cookie_parser深入
    • crypto模块之理论篇
    • express+cookie_parser签名机制深入剖析
    • express+session实现简易身份认证
    • https
    • log4js入门实例
    • node8_napi
    • 使用 async 控制并发
    • 使用 eventproxy 控制并发
    • 使用 superagent 与 cheerio 完成简单爬虫
    • 基于express+muter的文件上传
    • 将图片转成datauri嵌入到html
    • 常用中间件 body_parser 实现解析
    • 日志模块morgan
    • 服务端字符编解码&乱码处理 charset_enc_dec
    • 测试用例mocha,should,istanbul
    • 调试日志打印debug模块
  • Express

    • 1.1 Express概览篇
    • 1.2 Express
  • Koa2

    • 1.0 koa2概览篇
    • 1.1 快速开始
    • 1.2 async await使用
    • 1.3 koa2简析结构
    • 1.4 koa中间件开发与使用
    • 10.1 单元测试
    • 11.1 开发debug
    • 12.1 快速启动
    • 12.2 框架设计
    • 12.3 分层操作
    • 12.4 数据库设计
    • 12.5 路由设计
    • 12.6 webpack4环境搭建
    • 12.7 使用react
    • 12.8 登录注册功能实现
    • 12.9 session登录态判断处理
    • 13.1 import export使用
    • 2.1 原生koa2实现路由
    • 2.2 koa router中间
    • 3.1 GET请求数据获取
    • 3.2 POST请求数据获取
    • 3.3 koa bodyparser中间件
    • 4.1 原生koa2实现静态资源服务器
    • 4.2 koa static中间件
    • 5.1 koa2使用cookie
    • 5.2 koa2实现session
    • 6.1 koa2加载模板引擎
    • 6.2 ejs模板引擎
    • 7.1 busboy模块
    • 7.2 上传文件简单实现
    • 7.3 异步上传图片实现
    • 8.1 mysql模块
    • 8.2 async await封装使用mysql
    • 8.3 项目建表初始化
    • 9.1 原生koa2实现JSONP
    • 9.2 koa jsonp中间件
  • 其他

    • 操作数据库
    • Session 与 Token
    • Cookie、Session、Token、JWT
    • Socket

# 域名解析 dns

30 秒速记

  • 两套解析:dns.lookup() 走操作系统(读 hosts,和 ping 一样);dns.resolve*() 直接发 DNS 查询
  • lookup 底层是 getaddrinfo,跑在 libuv 线程池里(默认 4 个线程),解析慢会堵住 fs、crypto
  • http.get、net.connect 默认用的都是 lookup
  • 一个域名可能有多个 IPv4 / IPv6 地址,要处理顺序和失败回退
  • 解析结果会变:缓存要按 TTL 失效;做 SSRF 防护要校验最终连接的 IP

Node 的 dns 模块其实是两套东西:dns.lookup() 让操作系统帮你查,dns.resolve 系列自己去问 DNS 服务器。 lookup 的结果和你在终端里 ping 一个域名看到的一致,会读 /etc/hosts,http、net 这些模块发请求默认也是用它;但它底层调的是同步的 getaddrinfo,Node 只能把它扔进 libuv 线程池,默认只有 4 个线程,DNS 一慢就会把同池子的文件读写、加密操作一起拖慢。resolve4、resolveMx 这些走 c-ares 做真正的网络查询,不读 hosts,也不占线程池,还能拿到 TTL。

理解「域名解析 dns」时,要先明确输入、运行阶段和可观察输出。这里的交互实验会分别执行正常路径与失败路径,并把 Node.js 的关键状态变化和同屏代码对应起来;不能只记命令名称。

工程中还要验证退出码、资源清理和异常输入。属于 module 场景的配置应保持最小可复现,并在开发环境与 CI 中使用相同命令复跑,避免把平台差异或隐式默认值带到线上。

lookup 和 resolve 的差别,归根结底是「谁在查」:

const dns = require('dns');

// 1. 交给操作系统查:读 hosts、遵循系统解析配置,跑在 libuv 线程池
dns.lookup('localhost', (err, address, family) => {
  console.log('lookup:', address, family); // lookup: 127.0.0.1 4(或 ::1 6)
});

// 2. 自己发 DNS 报文查:不读 hosts,走 c-ares,不占线程池
dns.resolve4('example.com', { ttl: true }, (err, records) => {
  console.log('resolve4:', records); // [{ address: '93.x.x.x', ttl: 3600 }]
});

// 3. Promise 版本,现在写新代码推荐用这个
dns.promises.lookup('example.com').then(console.log);
@前端进阶之旅: 代码已经复制到剪贴板

几条要记住的事实:

  • http.request、net.connect、fetch 默认都走 dns.lookup,所以 hosts 里配的映射对它们生效。
  • Node 17 起 lookup 默认按系统返回的原始顺序给结果(verbatim),之前的版本会把 IPv4 排在前面;可以用 dns.setDefaultResultOrder('ipv4first') 改回来。
  • Node 20 起 net.connect 默认开启 autoSelectFamily,会对 IPv4 和 IPv6 地址做类似 Happy Eyeballs 的竞速,IPv6 不通时能更快回退。
  • lookup 本身没有缓存,也拿不到 TTL;需要缓存就自己做,按 TTL 过期。

面试官追问

追问 1线上 DNS 偶尔慢,结果读文件、bcrypt 加密也跟着变慢,为什么?
参考回答

dns.lookup 和 fs、crypto 共用 libuv 线程池,默认 4 个线程,几个慢解析就把池子占满了。可以调大 UV_THREADPOOL_SIZE,或者在请求里用自定义 lookup 加缓存,热点域名别每次都现查。

追问 2axios 请求内部服务,每次都要解析一遍域名,要不要做 DNS 缓存?
参考回答

高频调用值得做,但要按 TTL 过期,别永久缓存,否则服务迁移换 IP 之后一直打旧地址。可以用 cacheable-lookup 这类库挂到 http.Agent 上。

追问 3做 SSRF 防护,先 lookup 校验不是内网 IP 再发请求,够吗?
参考回答

不够,校验和真正连接时各解析一次,攻击者可以用 DNS 重绑定让第二次解析变成 127.0.0.1。要么把校验后的 IP 直接用来连接,要么在自定义 lookup 里校验,保证检查的就是实际连的那个地址。

追问 4测试里想断言 www.example.com 解析到某个固定 IP,靠谱吗?
参考回答

不靠谱,公网域名的 IP 随时会变,还受 CDN 调度影响。断言返回格式和地址族就够了;需要确定结果时,用 hosts 或者 mock 掉 dns.lookup。

# 域名解析:dns.lookup()

30 秒速记

  • dns.lookup(host, cb) 回调是 (err, address, family),默认只给一个地址
  • 传 { all: true } 拿全部地址,回调第二个参数变成数组 [{ address, family }]
  • family 是数字 4 或 6;也可以传 { family: 4 } 只要 IPv4
  • 默认结果顺序:Node 17 起按系统返回顺序,之前是 IPv4 优先
  • 解析成功不代表连得上,解析失败和连接失败要分开记

dns.lookup() 就是问操作系统「这个域名对应哪个 IP」,默认只回一个,想要全部就传 { all: true }。 默认回调签名是 (err, address, family),address 是字符串,family 是 4 或 6;加了 all: true 之后第二个参数变成数组,每项是 { address, family },family 参数就没了。所以同一个域名查出来只有一个 IP,不代表它真的只有一个,只是默认模式挑了第一个给你。还有个版本差异要知道:Node 17 之前会把 IPv4 排在前面,之后按系统给的原始顺序,有些机器上就会先拿到 IPv6。

比如我们要查询域名 www.qq.com 对应的ip,可以通过 dns.lookup() 。

var dns = require('dns');

dns.lookup('www.qq.com', function(err, address, family){
    if(err) throw err;
    console.log('例子A: ' + address);
});
@前端进阶之旅: 代码已经复制到剪贴板

输出如下:

例子A: 182.254.34.74
@前端进阶之旅: 代码已经复制到剪贴板

我们知道,同一个域名,可能对应多个不同的ip。那么,如何获取一个域名对应的多个ip呢?可以这样。

var dns = require('dns');
var options = {all: true};

dns.lookup('www.qq.com', options, function(err, address, family){
    if(err) throw err;
    console.log('例子B: ' + address);
});
@前端进阶之旅: 代码已经复制到剪贴板

输出如下:

例子B: [{"address":"182.254.34.74","family":4},{"address":"240e:e1:8100:28::2:16","family":6}]
@前端进阶之旅: 代码已经复制到剪贴板

面试官追问

追问 1默认 lookup 只打出一个 IP,能说明这个域名只有一个地址吗?
参考回答

不能,默认模式只返回第一个。加 { all: true } 才能看到全部,比如同时有 IPv4 和 IPv6 地址。

追问 2升级到 Node 18 之后,有些请求开始报 ECONNREFUSED ::1,怎么回事?
参考回答

Node 17 起 lookup 不再强制 IPv4 优先,localhost 先解析成了 ::1,而服务只监听了 127.0.0.1。要么服务监听 :: 或同时监听两种地址,要么连接时写 127.0.0.1,或者调用 dns.setDefaultResultOrder('ipv4first')。

追问 3传了 { all: true } 后还用 address.split('.') 处理,会出什么事?
参考回答

这时 address 是对象数组,不是字符串,直接报 split is not a function。两种模式的回调形态不同,封装时要统一成数组再往下传。

追问 4机器只有 IPv4 出口,怎么让 lookup 只返回 IPv4?
参考回答

传 { family: 4 },dns.lookup(host, { family: 4 }, cb)。http.request 也支持同名的 family 选项,会透传给 lookup。

追问 5dns.lookup 有 Promise 写法吗?
参考回答

有,const { address } = await require('dns').promises.lookup(host),Node 15 起也可以 require('dns/promises')。传

基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
  • 基础篇

    • 环境搭建
    • node部署
    • 基础应用
  • 内置模块

    • 1.0 本地路径处理 path
    • 2.0 文件系统操作 fs
    • 3.1 基础调试 console
    • 3.2 本地调试远程服务器上的Node代码
    • 4.1 网络服务 http
    • 4.2 网络服务 http res
    • 4.3 网络服务 http req
    • 4.4 网络服务 http server
    • 4.5 网络服务 http client
    • 4.6 网络服务 https
    • 4.7 网络TCP net
    • 4.8 网络UDP dgram
    • 4.9 域名解析 dns
      • 域名解析:dns.lookup()
      • 域名解析:dns.resolve4()
      • dns.lookup()跟dns.resolve4()的区别
      • 其他接口
      • 相关链接
    • 5.0 网络地址解析 url
    • 5.1 URL查询字符串 querystring
    • 6.1 流操作 stream
    • 6.2 逐行读取 readline
    • 7.1 进程相关 process
    • 7.2 子进程 child
    • 8.1 二进制数据 buffer
    • 8.2 二进制解码 string_decoder
    • 9.1 事件机制 events
    • 9.2 实用工具模块 util
    • 9.3 数据加密 crypto
    • 9.4 MD5入门介绍及crypto模块的应用
    • 9.4 资源压缩 zlib
    • 9.5 集群 cluster
    • 9.6 v8
  • 进阶篇

    • 5分钟入门非对称加密用法
    • cluster
    • cookie_parser深入
    • crypto模块之理论篇
    • express+cookie_parser签名机制深入剖析
    • express+session实现简易身份认证
    • https
    • log4js入门实例
    • node8_napi
    • 使用 async 控制并发
    • 使用 eventproxy 控制并发
    • 使用 superagent 与 cheerio 完成简单爬虫
    • 基于express+muter的文件上传
    • 将图片转成datauri嵌入到html
    • 常用中间件 body_parser 实现解析
    • 日志模块morgan
    • 服务端字符编解码&乱码处理 charset_enc_dec
    • 测试用例mocha,should,istanbul
    • 调试日志打印debug模块
  • Express

    • 1.1 Express概览篇
    • 1.2 Express
  • Koa2

    • 1.0 koa2概览篇
    • 1.1 快速开始
    • 1.2 async await使用
    • 1.3 koa2简析结构
    • 1.4 koa中间件开发与使用
    • 10.1 单元测试
    • 11.1 开发debug
    • 12.1 快速启动
    • 12.2 框架设计
    • 12.3 分层操作
    • 12.4 数据库设计
    • 12.5 路由设计
    • 12.6 webpack4环境搭建
    • 12.7 使用react
    • 12.8 登录注册功能实现
    • 12.9 session登录态判断处理
    • 13.1 import export使用
    • 2.1 原生koa2实现路由
    • 2.2 koa router中间
    • 3.1 GET请求数据获取
    • 3.2 POST请求数据获取
    • 3.3 koa bodyparser中间件
    • 4.1 原生koa2实现静态资源服务器
    • 4.2 koa static中间件
    • 5.1 koa2使用cookie
    • 5.2 koa2实现session
    • 6.1 koa2加载模板引擎
    • 6.2 ejs模板引擎
    • 7.1 busboy模块
    • 7.2 上传文件简单实现
    • 7.3 异步上传图片实现
    • 8.1 mysql模块
    • 8.2 async await封装使用mysql
    • 8.3 项目建表初始化
    • 9.1 原生koa2实现JSONP
    • 9.2 koa jsonp中间件
  • 其他

    • 操作数据库
    • Session 与 Token
    • Cookie、Session、Token、JWT
    • Socket
完整面试题地址:
作者:程序员poetry
扫码关注作者公众号:「前端进阶之旅」 每天分享技术干货
前端进阶之旅公众号二维码

域名解析 dns|Nodejs专题系列

# 域名解析 dns

30 秒速记

  • 两套解析:dns.lookup() 走操作系统(读 hosts,和 ping 一样);dns.resolve*() 直接发 DNS 查询
  • lookup 底层是 getaddrinfo,跑在 libuv 线程池里(默认 4 个线程),解析慢会堵住 fs、crypto
  • http.get、net.connect 默认用的都是 lookup
  • 一个域名可能有多个 IPv4 / IPv6 地址,要处理顺序和失败回退
  • 解析结果会变:缓存要按 TTL 失效;做 SSRF 防护要校验最终连接的 IP

Node 的 dns 模块其实是两套东西:dns.lookup() 让操作系统帮你查,dns.resolve 系列自己去问 DNS 服务器。 lookup 的结果和你在终端里 ping 一个域名看到的一致,会读 /etc/hosts,http、net 这些模块发请求默认也是用它;但它底层调的是同步的 getaddrinfo,Node 只能把它扔进 libuv 线程池,默认只有 4 个线程,DNS 一慢就会把同池子的文件读写、加密操作一起拖慢。resolve4、resolveMx 这些走 c-ares 做真正的网络查询,不读 hosts,也不占线程池,还能拿到 TTL。

理解「域名解析 dns」时,要先明确输入、运行阶段和可观察输出。这里的交互实验会分别执行正常路径与失败路径,并把 Node.js 的关键状态变化和同屏代码对应起来;不能只记命令名称。

工程中还要验证退出码、资源清理和异常输入。属于 module 场景的配置应保持最小可复现,并在开发环境与 CI 中使用相同命令复跑,避免把平台差异或隐式默认值带到线上。

lookup 和 resolve 的差别,归根结底是「谁在查」:

const dns = require('dns');

// 1. 交给操作系统查:读 hosts、遵循系统解析配置,跑在 libuv 线程池
dns.lookup('localhost', (err, address, family) => {
  console.log('lookup:', address, family); // lookup: 127.0.0.1 4(或 ::1 6)
});

// 2. 自己发 DNS 报文查:不读 hosts,走 c-ares,不占线程池
dns.resolve4('example.com', { ttl: true }, (err, records) => {
  console.log('resolve4:', records); // [{ address: '93.x.x.x', ttl: 3600 }]
});

// 3. Promise 版本,现在写新代码推荐用这个
dns.promises.lookup('example.com').then(console.log);
@前端进阶之旅: 代码已经复制到剪贴板

几条要记住的事实:

  • http.request、net.connect、fetch 默认都走 dns.lookup,所以 hosts 里配的映射对它们生效。
  • Node 17 起 lookup 默认按系统返回的原始顺序给结果(verbatim),之前的版本会把 IPv4 排在前面;可以用 dns.setDefaultResultOrder('ipv4first') 改回来。
  • Node 20 起 net.connect 默认开启 autoSelectFamily,会对 IPv4 和 IPv6 地址做类似 Happy Eyeballs 的竞速,IPv6 不通时能更快回退。
  • lookup 本身没有缓存,也拿不到 TTL;需要缓存就自己做,按 TTL 过期。

面试官追问

追问 1线上 DNS 偶尔慢,结果读文件、bcrypt 加密也跟着变慢,为什么?
参考回答

dns.lookup 和 fs、crypto 共用 libuv 线程池,默认 4 个线程,几个慢解析就把池子占满了。可以调大 UV_THREADPOOL_SIZE,或者在请求里用自定义 lookup 加缓存,热点域名别每次都现查。

追问 2axios 请求内部服务,每次都要解析一遍域名,要不要做 DNS 缓存?
参考回答

高频调用值得做,但要按 TTL 过期,别永久缓存,否则服务迁移换 IP 之后一直打旧地址。可以用 cacheable-lookup 这类库挂到 http.Agent 上。

追问 3做 SSRF 防护,先 lookup 校验不是内网 IP 再发请求,够吗?
参考回答

不够,校验和真正连接时各解析一次,攻击者可以用 DNS 重绑定让第二次解析变成 127.0.0.1。要么把校验后的 IP 直接用来连接,要么在自定义 lookup 里校验,保证检查的就是实际连的那个地址。

追问 4测试里想断言 www.example.com 解析到某个固定 IP,靠谱吗?
参考回答

不靠谱,公网域名的 IP 随时会变,还受 CDN 调度影响。断言返回格式和地址族就够了;需要确定结果时,用 hosts 或者 mock 掉 dns.lookup。

# 域名解析:dns.lookup()

30 秒速记

  • dns.lookup(host, cb) 回调是 (err, address, family),默认只给一个地址
  • 传 { all: true } 拿全部地址,回调第二个参数变成数组 [{ address, family }]
  • family 是数字 4 或 6;也可以传 { family: 4 } 只要 IPv4
  • 默认结果顺序:Node 17 起按系统返回顺序,之前是 IPv4 优先
  • 解析成功不代表连得上,解析失败和连接失败要分开记

dns.lookup() 就是问操作系统「这个域名对应哪个 IP」,默认只回一个,想要全部就传 { all: true }。 默认回调签名是 (err, address, family),address 是字符串,family 是 4 或 6;加了 all: true 之后第二个参数变成数组,每项是 { address, family },family 参数就没了。所以同一个域名查出来只有一个 IP,不代表它真的只有一个,只是默认模式挑了第一个给你。还有个版本差异要知道:Node 17 之前会把 IPv4 排在前面,之后按系统给的原始顺序,有些机器上就会先拿到 IPv6。

比如我们要查询域名 www.qq.com 对应的ip,可以通过 dns.lookup() 。

var dns = require('dns');

dns.lookup('www.qq.com', function(err, address, family){
    if(err) throw err;
    console.log('例子A: ' + address);
});
@前端进阶之旅: 代码已经复制到剪贴板

输出如下:

例子A: 182.254.34.74
@前端进阶之旅: 代码已经复制到剪贴板

我们知道,同一个域名,可能对应多个不同的ip。那么,如何获取一个域名对应的多个ip呢?可以这样。

var dns = require('dns');
var options = {all: true};

dns.lookup('www.qq.com', options, function(err, address, family){
    if(err) throw err;
    console.log('例子B: ' + address);
});
@前端进阶之旅: 代码已经复制到剪贴板

输出如下:

例子B: [{"address":"182.254.34.74","family":4},{"address":"240e:e1:8100:28::2:16","family":6}]
@前端进阶之旅: 代码已经复制到剪贴板

面试官追问

追问 1默认 lookup 只打出一个 IP,能说明这个域名只有一个地址吗?
参考回答

不能,默认模式只返回第一个。加 { all: true } 才能看到全部,比如同时有 IPv4 和 IPv6 地址。

追问 2升级到 Node 18 之后,有些请求开始报 ECONNREFUSED ::1,怎么回事?
参考回答

Node 17 起 lookup 不再强制 IPv4 优先,localhost 先解析成了 ::1,而服务只监听了 127.0.0.1。要么服务监听 :: 或同时监听两种地址,要么连接时写 127.0.0.1,或者调用 dns.setDefaultResultOrder('ipv4first')。

追问 3传了 { all: true } 后还用 address.split('.') 处理,会出什么事?
参考回答

这时 address 是对象数组,不是字符串,直接报 split is not a function。两种模式的回调形态不同,封装时要统一成数组再往下传。

追问 4机器只有 IPv4 出口,怎么让 lookup 只返回 IPv4?
参考回答

传 { family: 4 },dns.lookup(host, { family: 4 }, cb)。http.request 也支持同名的 family 选项,会透传给 lookup。

追问 5dns.lookup 有 Promise 写法吗?
参考回答

有,const { address } = await require('dns').promises.lookup(host),Node 15 起也可以 require('dns/promises')。传 { all: true } 时返回的就是数组。

# 域名解析:dns.resolve4()

30 秒速记

  • dns.resolve4(host, cb) 直接查 A 记录,回调第二个参数始终是数组
  • IPv6 对应 dns.resolve6(),查 AAAA 记录
  • 不读 hosts,不占 libuv 线程池,走 c-ares 发真正的网络查询
  • 传 { ttl: true } 能拿到每条记录的 TTL,做缓存很有用
  • 查不到时报 ENOTFOUND / ENODATA,批量解析要单条兜错,别一个失败全挂

dns.resolve4() 是直接去 DNS 服务器查 A 记录,回调里拿到的永远是一个地址数组。 和 lookup 不同,它不经过操作系统的解析逻辑,hosts 里的配置对它无效,也不会占用线程池。一个域名有多条 A 记录很常见,做负载均衡或者容灾都靠这个,所以别只取第一项就当全部。要 IPv6 就换 resolve6;想做缓存就传 { ttl: true },结果会变成 [{ address, ttl }],按 ttl 过期最合理。

上文的例子,也可以通过 dns.resolve4() 来实现。

var dns = require('dns');

dns.resolve4('id.qq.com', function(err, address){
    if(err) throw err;
    console.log( JSON.stringify(address) );
});
@前端进阶之旅: 代码已经复制到剪贴板

输出如下:

["61.151.186.39","101.227.139.179"]
@前端进阶之旅: 代码已经复制到剪贴板

如果要获取IPv6的地址,接口也差不多,不赘述。

← 4.8 网络UDP dgram5.0 网络地址解析 url →

fe